﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Serialization;
using System.Text.RegularExpressions;
using System.Reflection;

namespace Utils
{
    public static class TypeExtensions
    {
        public static T GetCustomAttribute<T>(this Type type)
        {
            var attribute = (T)type.GetCustomAttributes(true).OfType<T>().First();

            return attribute;
        }

        public static IEnumerable<T> GetCustomAttributes<T>(this Type type)
        {
            var attributes = type.GetCustomAttributes(true).OfType<T>();

            return attributes;
        }

        public static bool HasCustomAttribute<T>(this Type type)
        {
            return type.GetCustomAttributes(true).OfType<T>().Count() > 0;
        }

        public static T GetCustomAttribute<T>(this MemberInfo member)
        {
            var attribute = (T)member.GetCustomAttributes(true).OfType<T>().Single();

            return attribute;
        }

        public static T GetCustomAttribute<T>(this Type type, string memberName)
        {
            var memberInfo = type.GetMember(memberName).First();
            var attribute = memberInfo.GetCustomAttribute<T>();

            return attribute;
        }

        public static T GetCustomAttribute<T>(this Type enumType, object value)
        {
            var memberInfo = (MemberInfo) enumType.GetFields().Single(f =>
            {
                return f.Name != "value__" && ((int) f.GetRawConstantValue()) == (int) value;
            });

            var attribute = memberInfo.GetCustomAttribute<T>();

            return attribute;
        }

        public static List<Type> GetAncestors(this Type type, bool includeMe)
        {
            var list = new List<Type>();
            var parent = type.BaseType;

            if (includeMe)
            {
                list.Add(type);
            }

            while (parent != null)
            {
                list.Add(parent);
                parent = parent.BaseType;
            }

            return list;
        }

        public static bool IsQualifiedName(string name)
        {
            var regex = new Regex(@"^(?<assembly>[\w\.]+)(,\s?Version=(?<version>\d+\.\d+\.\d+\.\d+))?(,\s?Culture=(?<culture>\w+))?(,\s?PublicKeyToken=(?<token>\w+))?$");

            if (regex.IsMatch(name))
            {
                var match = regex.Match(name);
                var assemblyName = match.Groups["assembly"].Value;
                var version = match.Groups["version"].Value;
                var culture = match.Groups["culture"].Value;
                var token = match.Groups["token"].Value;

                return AnyHasValue(version, culture, token);
            }
            else
            {
                return false;
            }
        }

        public static bool Implements<T>(this Type type)
        {
            return type.GetAncestors(true).Any(t => t.GetInterfaces().Any(i => i.FullName == typeof(T).FullName));
        }

        public static bool AnyHasValue(params object[] values)
        {
            foreach (var value in values)
            {
                if (value is string && !string.IsNullOrEmpty((string) value))
                {
                    return true;
                }
            }

            return false;
        }

        public static bool IsOneOf(this object valueTest, params object[] values)
        {
            foreach (var value in values)
            {
                if (value is string && valueTest is string && ((string) valueTest) == ((string) value))
                {
                    return true;
                }
            }

            return false;
        }
        
        public static bool IsSerializable(this Type type)
        {
            try
            {
                if (type.IsValueType || type == typeof(string))
                {
                    return true;
                }
                else if (type.IsArray)
                {
                    var elementType = type.GetElementType();

                    return elementType.IsSerializable();
                }
                else
                {
                    var instance = Activator.CreateInstance(type);
                    var stream = Serialize(instance);
                    var newInstance = instance.Clone(type);

                    if (instance.GetType() == newInstance.GetType())
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            catch
            {
                return false;
            }
        }

        public static Stream Serialize(object source) 
        {
            XmlSerializer serializer = new XmlSerializer(source.GetType());
            Stream stream = new MemoryStream(); 
            serializer.Serialize(stream, source); 
            return stream; 
        }  
        
        public static T Deserialize<T>(this Stream stream) 
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            stream.Position = 0;

            return (T)serializer.Deserialize(stream); 
        }

        public static object Deserialize(this Stream stream, Type type)
        {
            XmlSerializer serializer = new XmlSerializer(type);
            stream.Position = 0;

            return serializer.Deserialize(stream);
        }  
       
        public static T Clone<T>(this object source) 
        { 
            return Deserialize<T>(Serialize(source)); 
        }

        public static object Clone(this object source, Type type)
        {
            return Deserialize(Serialize(source), type);
        }
    }
}
